#include "Engine.h"
#include <Windows.h>
#include <gl/GL.h>
#include <gl/GLU.h>
#include "SDL.h"
#include "GameTimer.h"
#include "KeyBoard.h"
#include "OpenGLRenderer.h"
#include "OBJModelLoader.h"
#include "Vectors.h"
#include "PhysicsSimulator.h"

namespace
{
	static SDL_Surface *surface;
	static SDL_Event evt;
	static int videoFlags;
	static GLsizei screenWidth, screenHeight;
	static bool initialized = false;

	void resizeWindow(GLsizei width, GLsizei height);
	steam::intake::KeyCode getLocalKey(SDLKey sdlKey);

	void createWindow(int width, int height, bool fullscreen)
	{
		const SDL_VideoInfo *videoInfo;

		if (SDL_Init(SDL_INIT_VIDEO) < 0)
		{
			std::cerr << SDL_GetError() << std::endl;
		}

		videoInfo = SDL_GetVideoInfo();

		if (!videoInfo)
		{
			std::cerr << SDL_GetError() << std::endl;
		}

		videoFlags  = SDL_OPENGL;          /* Enable OpenGL in SDL */
		videoFlags |= SDL_GL_DOUBLEBUFFER; /* Enable double buffering */
		videoFlags |= SDL_HWPALETTE;       /* Store the palette in hardware */
		videoFlags |= SDL_RESIZABLE;       /* Enable window resizing */

		if (videoInfo->hw_available)
			videoFlags |= SDL_HWSURFACE;
		else
			videoFlags |= SDL_SWSURFACE;

		if (videoInfo->blit_hw)
			videoFlags |= SDL_HWACCEL;

		if (fullscreen)
			videoFlags |= SDL_FULLSCREEN;

		SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

		surface = SDL_SetVideoMode(width, height, 16, videoFlags);

		if (!surface)
		{
			std::cerr << SDL_GetError() << std::endl;
		}

		resizeWindow(width, height);
	}

	void resizeWindow(GLsizei width, GLsizei height)
	{
		if (height == 0)
		{
			height = 1;
		}

		glClearColor(0.0, 0.0, 0.0, 1.0);
		glMatrixMode(GL_PROJECTION);

		float fov = 45.f;
		float aspectRatio = float(width)/float(height);
		float nearZ = 0.1f;
		float farZ = 1000.0f;

		gluPerspective(fov, aspectRatio, nearZ, farZ);

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
	}

	steam::intake::KeyCode getLocalKey( SDLKey sdlKey )
	{
		switch(sdlKey)
		{
		case SDLK_0:
			return steam::intake::SK_0;
		case SDLK_1:
			return steam::intake::SK_1;
		case SDLK_2:
			return steam::intake::SK_2;
		case SDLK_3:
			return steam::intake::SK_3;
		case SDLK_4:
			return steam::intake::SK_4;
		case SDLK_5:
			return steam::intake::SK_5;
		case SDLK_6:
			return steam::intake::SK_6;
		case SDLK_7:
			return steam::intake::SK_7;
		case SDLK_8:
			return steam::intake::SK_8;
		case SDLK_9:
			return steam::intake::SK_9;
		case SDLK_UP:
			return steam::intake::SK_UP;
		case SDLK_DOWN:
			return steam::intake::SK_DOWN;
		case SDLK_LEFT:
			return steam::intake::SK_LEFT;
		case SDLK_RIGHT:
			return steam::intake::SK_RIGHT;
		case SDLK_w:
			return steam::intake::SK_W;
		case SDLK_a:
			return steam::intake::SK_A;
		case SDLK_s:
			return steam::intake::SK_S;
		case SDLK_d:
			return steam::intake::SK_D;
		case SDLK_SPACE:
			return steam::intake::SK_SPACE;
		case SDLK_q:
			return steam::intake::SK_Q;
		case SDLK_e:
			return steam::intake::SK_E;
		case SDLK_z:
			return steam::intake::SK_Z;
		case SDLK_c:
			return steam::intake::SK_C;
		case SDLK_i:
			return steam::intake::SK_I;
		case SDLK_j:
			return steam::intake::SK_J;
		case SDLK_k:
			return steam::intake::SK_K;
		case SDLK_l:
			return steam::intake::SK_L;
		case SDLK_ESCAPE:
			return steam::intake::SK_ESCAPE;
		default:
			return steam::intake::SK_UNKNOWN;
		}
		return steam::intake::SK_UNKNOWN;
	}

}

namespace steam
{
	namespace core
	{

		Engine& Engine::create(int resolution_width, int resolution_height, bool fullscreen)
		{
			static core::Engine engine;

			if ( ! initialized )
			{
				createWindow(resolution_width, resolution_height, fullscreen);
				engine.init();
				initialized = true;
			}

			return engine;
		}

		moon::IRenderer& Engine::getRenderer()
		{
			return *m_renderer;
		}

		const EntityFactory* Engine::getEntityFactory() const
		{
			return m_entityFactory;
		}

		ITimer& Engine::getTimer()
		{
			return *m_gameTimer;
		}

		const IEventProcessor* Engine::getEventProcessor() const
		{
			return m_eventProcessor;
		}

		wheel::IPhysicsSimulator& Engine::getPhysicsSimulator()
		{
			return *m_physicsSimulator;
		}

		bool Engine::run()
		{
			SDL_GL_SwapBuffers();

			while (SDL_PollEvent(&evt))
			{
				switch (evt.type)
				{
				case SDL_QUIT:
					m_quit = true;
				case SDL_KEYDOWN:
					{
						m_inputEventHandler->injectKeyDown(getLocalKey(evt.key.keysym.sym));
					}
					break;
				case SDL_KEYUP:
					{		
						m_inputEventHandler->injectKeyUp(getLocalKey(evt.key.keysym.sym));
					}
					break;
				case SDL_MOUSEBUTTONDOWN:
					{
						
					}
					break;
				case SDL_MOUSEBUTTONUP:
					{
						
					}
					break;
				case SDL_MOUSEMOTION:
					{

					}
					break;
				default:
					break;
				}
			}

			// TODO

			m_gameTimer->tick();

			return ! m_quit;
		}

		void Engine::pause()
		{

		}

		void Engine::destroy()
		{
			m_quit = true;
		}

		Engine::Engine() : m_quit(false), m_renderer(NULL), m_inputEventHandler(NULL), m_eventProcessor(NULL), 
			m_gameTimer(NULL), m_entityFactory(NULL), m_assetLoader(NULL), m_physicsSimulator(NULL)
		{
			
		}

		Engine::~Engine()
		{
			destroy();
			delete m_gameTimer;
			delete m_inputEventHandler;
			delete m_renderer;
			delete m_assetLoader;
			delete m_physicsSimulator;
		}

		coal::AssetLoader& Engine::getAssetLoader()
		{
			return *m_assetLoader;
		}

		IInputEventHandler& Engine::getInputHandler()
		{
			return *m_inputEventHandler;
		}

		void Engine::init()
		{
			m_gameTimer = new GameTimer;
			m_inputEventHandler = new InputEventHandler;
			m_renderer = new moon::OpenGLRenderer;
			m_assetLoader = new coal::AssetLoader("..\\..\\Resources\\");			
			m_assetLoader->addModelLoader("OBJ", new coal::OBJModelLoader());
			m_physicsSimulator = new wheel::PhysicsSimulator(10,10);
		}

	}
}